package com.yc.framework.web.service;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.github.benmanes.caffeine.cache.Cache;
import com.yc.common.constant.HttpStatus;
import com.yc.common.constant.TokenConstants;
import com.yc.common.exceptions.ServiceException;
import com.yc.common.constant.Constants;
import com.yc.common.core.domain.model.LoginUser;
import com.yc.common.utils.JwtUtil;
import com.yc.common.core.redis.RedisCache;
import com.yc.common.utils.StringUtil;
import com.yc.common.utils.WebUtil;
import com.yc.framework.web.domain.TokenInfo;
import com.yc.system.service.ISysUserService;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * Token服务 - 修复完整版
 */
@Component
@Slf4j
public class TokenService {

    // 缓存键前缀
    private static final String ACCESS_TOKEN_KEY = TokenConstants.ACCESS_TOKEN_KEY;
    private static final String REFRESH_TOKEN_KEY = TokenConstants.REFRESH_TOKEN_KEY;
    private static final String BACKUP_PREFIX = TokenConstants.BACKUP_PREFIX;

    // 用户当前有效token映射
    private final Map<Long, String> userCurrentTokenMap = new ConcurrentHashMap<>();

    @Autowired
    private Cache<String, LoginUser> tokenCache;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISysUserService sysUserService;

    /**
     * 创建Token（登录或刷新时调用）
     */
    public TokenInfo createToken(LoginUser loginUser, String... additional) {
        TokenInfo tokenInfo = loginUser.getTokenInfo();
        String tokenId = generateShortUUID();

        // 更新用户当前有效token
        userCurrentTokenMap.put(loginUser.getUser().getId(), tokenId);
        tokenInfo.setTokenId(tokenId);

        // 生成访问令牌
        tokenInfo.setAccessToken(createAccessToken(loginUser));

        // 如果是登录操作生成新刷新令牌，刷新操作沿用旧刷新令牌
        tokenInfo.setRefreshToken(additional == null || additional.length == 0 ?
                createRefreshToken(loginUser) : additional[0]);

        // 缓存用户信息
        cacheLoginUser(loginUser);

        return tokenInfo;
    }

    /**
     * 创建访问令牌(JWT)
     */
    public String createAccessToken(LoginUser loginUser) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("uid", loginUser.getUser().getId());
        claims.put("typ", "a"); // access token类型标识
        claims.put("jti", loginUser.getTokenInfo().getTokenId());

        if (loginUser.getRoles() != null && !loginUser.getRoles().isEmpty()) {
            claims.put("roles", loginUser.getRoles());
        }

        return JwtUtil.createJWT(
                JSON.toJSONString(claims),
                TimeUnit.MINUTES.toMillis(TokenConstants.ACCESS_TOKEN_EXPIRE)
        );
    }

    /**
     * 创建刷新令牌(JWT)
     */
    private String createRefreshToken(LoginUser loginUser) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("uid", loginUser.getUser().getId());
        claims.put("typ", "r"); // refresh token类型标识
        claims.put("tid", loginUser.getTokenInfo().getTokenId());

        return JwtUtil.createJWT(
                JSON.toJSONString(claims),
                TimeUnit.DAYS.toMillis(TokenConstants.REFRESH_TOKEN_EXPIRE)
        );
    }

    /**
     * 缓存登录用户信息
     */
    public void cacheLoginUser(LoginUser loginUser) {
        String tokenId = loginUser.getTokenInfo().getTokenId();
        Long userId = loginUser.getUser().getId();

        try {
            // 存储简化信息到Redis
            Map<String, Object> tokenInfo = new HashMap<>();
            tokenInfo.put("userId", userId);
            tokenInfo.put("username", loginUser.getUsername());
            tokenInfo.put("status", loginUser.getUser().getStatus());
            tokenInfo.put("loginTime", System.currentTimeMillis());

            // 主缓存(访问令牌)
            redisCache.setCacheObject(
                    getAccessTokenKey(tokenId),
                    tokenInfo,
                    TokenConstants.ACCESS_TOKEN_EXPIRE,
                    TimeUnit.MINUTES);

            // 用户最后登录信息(Hash结构)
            String userKey = "user:" + userId;
            redisCache.setCacheHash(userKey, "lastTokenId", tokenId);
            redisCache.setCacheHash(userKey, "lastLoginTime", System.currentTimeMillis());
            redisCache.expire(userKey, TokenConstants.REFRESH_TOKEN_EXPIRE, TimeUnit.DAYS);

            // 本地缓存
            tokenCache.put(tokenId, loginUser);

            // 刷新令牌白名单
            String refreshKey = "refresh:" + userId;
            redisCache.setCacheHash(refreshKey, tokenId, System.currentTimeMillis());
            redisCache.expire(refreshKey, TokenConstants.REFRESH_TOKEN_EXPIRE, TimeUnit.DAYS);

        } catch (Exception e) {
            log.error("令牌存储失败", e);
            throw new ServiceException("令牌存储失败", HttpStatus.ERROR);
        }
    }

    /**
     * 刷新访问令牌
     */
    public TokenInfo refreshAccessToken(String refreshToken) {
        // 1. 验证基础格式
        Claims claims = validateRefreshToken(refreshToken);
        JSONObject payload = JSON.parseObject(claims.getSubject());

        // 2. 解析关键信息
        Long userId = payload.getLong("uid");
        String oldTokenId = payload.getString("tid");

        if (userId == null || userId <= 0 || StringUtil.isEmpty(oldTokenId)) {
            throw new ServiceException("无效的刷新令牌", HttpStatus.UNAUTHORIZED);
        }

        // 3. 验证刷新令牌有效性
        String refreshKey = "refresh:" + userId;
        if (!redisCache.hasHashKey(refreshKey, oldTokenId)) {
            throw new ServiceException("刷新令牌已失效", HttpStatus.UNAUTHORIZED);
        }

        // 4. 获取用户信息(含降级处理)
        LoginUser loginUser = getLoginUser(oldTokenId);
        if (loginUser == null) {
            loginUser = sysUserService.createLoginUser(userId);
            if (loginUser == null) {
                throw new ServiceException("用户不存在或已被禁用", HttpStatus.UNAUTHORIZED);
            }
        }

        // 5. 生成新令牌
        try {
            String newTokenId = generateShortUUID();
            loginUser.getTokenInfo().setTokenId(newTokenId);

            // 创建新令牌(沿用旧刷新令牌)
            TokenInfo tokenInfo = createToken(loginUser, refreshToken);

            // 清理旧令牌
            cleanOldToken(userId, oldTokenId);

            // 更新当前令牌映射
            userCurrentTokenMap.put(userId, newTokenId);

            return tokenInfo;
        } catch (Exception e) {
            log.error("刷新令牌失败", e);
            throw new ServiceException("刷新令牌失败", HttpStatus.ERROR);
        }
    }

    /**
     * 获取登录用户信息
     */
    public LoginUser getLoginUser(String tokenId) {
        if (StringUtil.isEmpty(tokenId)) {
            return null;
        }

        // 1. 检查本地缓存
        LoginUser loginUser = tokenCache.getIfPresent(tokenId);
        if (loginUser != null) {
            return loginUser;
        }

        // 2. 检查Redis主缓存
        try {
            loginUser = redisCache.getCacheObject(getAccessTokenKey(tokenId));
            if (loginUser != null) {
                tokenCache.put(tokenId, loginUser); // 回填本地缓存
                return loginUser;
            }
        } catch (Exception e) {
            log.error("Redis主缓存读取失败", e);
        }

        // 3. 检查Redis备份缓存
        try {
            loginUser = redisCache.getCacheObject(getBackupPrefix(tokenId));
            if (loginUser != null) {
                tokenCache.put(tokenId, loginUser); // 回填本地缓存
                return loginUser;
            }
        } catch (Exception e) {
            log.error("Redis备份缓存读取失败", e);
        }

        return null;
    }

    /**
     * 从当前请求获取登录用户
     */
    public LoginUser getLoginUser() {
        String token = WebUtil.getToken();
        if (StringUtil.isEmpty(token)) {
            return null;
        }

        try {
            Claims claims = JwtUtil.parseJWT(token);
            JSONObject payload = JSON.parseObject(claims.getSubject());

            if (!"a".equals(payload.getString("typ"))) {
                return null;
            }

            String tokenId = payload.getString("jti");
            return getLoginUser(tokenId);
        } catch (Exception e) {
            log.warn("令牌解析失败", e);
            return null;
        }
    }

    /**
     * 从HttpServletRequest获取登录用户
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        String token = getTokenFromRequest(request);
        return getLoginUser(token);
    }

    /**
     * 删除令牌并加入黑名单
     */
    public void deleteToken(String tokenId) {
        if (StringUtil.isEmpty(tokenId)) {
            return;
        }

        try {
            LoginUser loginUser = getLoginUser(tokenId);
            if (loginUser == null) {
                return;
            }

            Long userId = loginUser.getUser().getId();

            // 加入黑名单(基于剩余有效期)
            long ttlMillis = loginUser.getExpireTime() - System.currentTimeMillis();
            if (ttlMillis > 0) {
                blacklistAccessToken(tokenId);
            }

            // 更新用户登出时间
            loginUser.getUser().setLastLogoutTime(new Date());
            sysUserService.updateLogoutTime(loginUser.getUser());

            // 清理所有缓存
            cleanOldToken(userId, tokenId);
            userCurrentTokenMap.remove(userId);

        } catch (Exception e) {
            log.error("删除令牌失败", e);
            throw new ServiceException("删除令牌失败", HttpStatus.ERROR);
        }
    }

    /**
     * 清理旧令牌相关缓存
     */
    private void cleanOldToken(Long userId, String tokenId) {
        try {
            // 删除访问令牌缓存
            redisCache.deleteObject(getAccessTokenKey(tokenId));
            redisCache.deleteObject(getBackupPrefix(tokenId));
            tokenCache.invalidate(tokenId);

            // 从刷新令牌白名单移除
            redisCache.deleteHashKey("refresh:" + userId, tokenId);
        } catch (Exception e) {
            log.warn("清理旧令牌缓存失败", e);
        }
    }

    /**
     * 验证刷新令牌有效性
     */
    private Claims validateRefreshToken(String token) {
        if (StringUtil.isEmpty(token)) {
            throw new ServiceException("刷新令牌不能为空", HttpStatus.UNAUTHORIZED);
        }

        try {
            Claims claims = JwtUtil.parseJWT(token);
            JSONObject payload = JSON.parseObject(claims.getSubject());

            if (!"r".equals(payload.getString("typ"))) {
                throw new ServiceException("非刷新令牌", HttpStatus.UNAUTHORIZED);
            }

            return claims;
        } catch (Exception e) {
            log.warn("刷新令牌验证失败", e);
            throw new ServiceException("刷新令牌无效", HttpStatus.UNAUTHORIZED);
        }
    }

    /**
     * 生成短UUID
     */
    private String generateShortUUID() {
        return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
    }

    /**
     * 从请求头获取令牌
     */
    private String getTokenFromRequest(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        if (StringUtil.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
            token = token.substring(Constants.TOKEN_PREFIX.length());
        }
        return token;
    }

    /**
     * 将访问令牌加入黑名单
     */
    public void blacklistAccessToken(String jti) {
        String blacklistKey = "blacklist:access:" + jti;
        redisCache.setCacheObject(blacklistKey, true, 1L, TimeUnit.SECONDS);
    }

    /**
     * 检查令牌是否在黑名单中
     */
    public boolean isAccessTokenBlacklisted(String jti) {
        return redisCache.getCacheObject("blacklist:access:" + jti) != null;
    }

    /**
     * 检查是否为当前有效令牌
     */
    public boolean isCurrentToken(Long userId, String tokenId) {
        String currentTokenId = userCurrentTokenMap.get(userId);
        return currentTokenId != null && currentTokenId.equals(tokenId);
    }

    // 缓存键构建方法
    private String getAccessTokenKey(String tokenId) {
        return ACCESS_TOKEN_KEY + tokenId;
    }

    private String getRefreshTokenKey(String tokenId) {
        return REFRESH_TOKEN_KEY + tokenId;
    }

    private String getBackupPrefix(String tokenId) {
        return BACKUP_PREFIX + tokenId;
    }
}